<template>
    <div>
            <div ref="terminalBox" style="height: 100vh;"></div>
        <!-- 上传文件显示组件 -->
        <n-modal v-model:show="showModal" :mask-closable="false">
            <n-card style="width: 600px;" title="上传文件" :bordered="false" size="huge">
                <div class="upload">
                    <n-upload
                        :default-upload="false"
                        action="/aaa"
                        @change="changFileList"
                        multiple
                        :max="10"
                    >
                        <n-upload-dragger>
                            <div style="margin-bottom: 12px;">
                                <n-icon size="48" :depth="3">
                                    <archive-icon />
                                </n-icon>
                            </div>
                            <n-text style="font-size: 16px;">点击或者拖动文件到该区域来上传</n-text>
                            <n-p
                                depth="3"
                                style="margin: 8px 0 0 0;"
                            >请不要上传敏感数据，比如你的银行卡号和密码，信用卡号有效期和安全码</n-p>
                        </n-upload-dragger>
                    </n-upload>
                </div>
                <template #footer>
                    <n-space>
                        <n-button type="info" @click="ConfirmUpload">上 传</n-button>
                        <n-button @click="uploadFilecancel">取 消</n-button>
                    </n-space>
                </template>
            </n-card>
        </n-modal>
    </div>
</template>

<script setup>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { ref, onMounted } from 'vue'
import { ArchiveOutline as ArchiveIcon } from '@vicons/ionicons5'
// import 'nora-zmodemjs'
import '../../net/zmodem.devel'
import { useMessage } from "naive-ui";
import CryptoJS from "crypto-js";

import getUrl from '../../net/getUrl'

const message = useMessage();
let terminalBox = ref(null)
let showModal = ref(false)
let fileList = ref(null)
let term
let socket
let upZsession

function changFileList(f) {
    fileList.value = f.fileList
}

//字符串转base64
function getEncode64(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
        }));
}

//取消文件上传
function uploadFilecancel(e) {
    showModal.value = false
    if (upZsession == 'undefined') {
        message.error('会话不存在！')
        return
    }
    try {
        // zsession 每 5s 发送一个 ZACK 包，5s 后会出现提示最后一个包是 ”ZACK“ 无法正常关闭
        // 这里直接设置 _last_header_name 为 ZRINIT，就可以强制关闭了
        upZsession._last_header_name = "ZRINIT";
        upZsession.close();

    } catch (e) {
        console.log(e);
    }
}

// 上传文件按钮监听
function ConfirmUpload(e) {
    if (upZsession == 'undefined') {
        message.error('会话不存在！')
    }
    if (fileList.value.length <= 0) {
        message.error('请先选择需要上传的文件！')
        return
    }

    if (fileList.value.length > 10) {
        message.error('上传的文件不能超过10个！')
        return
    }

    let files = []
    for (let i of fileList.value) {
        // console.log(i.file.size)
        if (i.file.size <= 2048 * 1024 * 1024) {
            files.push(i.file)
        } else {
            message.warning(`${i.name} 超过 2048 MB, 无法上传`)
        }
    }

    showModal.value = false
    uploadFile(files)
}



//保存文件
function saveFile(xfer, buffer) {
    return Zmodem.Browser.save_to_disk(buffer, xfer.get_details().name);
}
//校验大小
function bytesHuman(bytes, precision) {
    if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
        return '-'
    }
    if (bytes === 0) return '0';
    if (typeof precision === 'undefined') precision = 1;
    const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
    const num = Math.floor(Math.log(bytes) / Math.log(1024));
    const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
    return `${value} ${units[num]}`
}

//实时显示进度
async function updateProgress(xfer, action = 'upload') {
    let detail = xfer.get_details();
    let name = detail.name;
    let total = detail.size;
    let offset = xfer.get_offset();
    let percent;
    if (total === 0 || total === offset) {
        percent = 100
    } else {
        percent = Math.round(offset / total * 100);
    }
    // term.write("\r" + name + ": " + total + " " + offset + " " + percent + "% ");
    term.write("\r" + action + ' ' + name + ": " + bytesHuman(offset) + " " + bytesHuman(total) + " " + percent + "% ");
    console.log("\r" + action + ' ' + name + ": " + bytesHuman(offset) + " " + bytesHuman(total) + " " + percent + "% ");
}

//下载
function downloadFile(zsession) {
    zsession.on("offer", function (xfer) {
        function on_form_submit() {
            //跳过不能下载的文件
            if (xfer.get_details().size > 2048 * 1024 * 1024) {
                xfer.skip();
                message.warning(`sz命令不允许下载  ${xfer.get_details().name} ，文件超过 2 GB`);
                return
            }

            let FILE_BUFFER = [];
            xfer.on("input", (payload) => {
                updateProgress(xfer, "下载");
                FILE_BUFFER.push(new Uint8Array(payload));
            });

            //下载完成的回调函数
            xfer.accept().then(
                () => {
                    saveFile(xfer, FILE_BUFFER);
                    term.write("\r\n");
                    //一般做审计用
                    socket.send(JSON.stringify({ type: "ignore", data: getEncode64(xfer.get_details().name + "(" + xfer.get_details().size + ") 文件下载成功") }));
                },
                console.error.bind(console)
            );
        }
        on_form_submit();
    });
    let promise = new Promise((res) => {
        zsession.on("session_end", () => {
            res();
        });
    });
    zsession.start();
    return promise;
}

//上传
function uploadFile(files) {
    new Promise((res) => {
        // Zmodem.Browser.send_files(upZsession, files, {
        Zmodem.Browser.send_block_files(upZsession, files, {
            on_offer_response(obj, xfer) {
                if (xfer) {
                    // term.write("\r\n");
                } else {
                    term.write(obj.name + ' 被跳过上传\r\n')
                }
            },
            on_progress(obj, xfer) {
                console.log(11111)
                updateProgress(xfer, '上传')
            },
            on_file_complete(obj) {
                term.write('\r\n')
                socket.send(
                    JSON.stringify({
                        type: 'ignore',
                        data: getEncode64(
                            obj.name + '(' + obj.size + ') 文件上传成功！'
                        ),
                    })
                )
            },
        })
            .then(upZsession.close.bind(upZsession), console.error.bind(console))
            .then(() => {
                res()
            })
    }).catch(console.error.bind(console)).then(() => {
    });
}

// dom 挂载后 回调
onMounted(() => {


    //创建一个客户端
    term = new Terminal({
        rendererType: 'canvas', //使用这个能解决vim不显示或乱码
        cursorBlink: true,
        cursorStyle: "bar",
    })
    // term.write
    // 将客户端挂载到dom上
    const fitAddon = new FitAddon()
    term.loadAddon(fitAddon)
    term.open(terminalBox.value)
    fitAddon.fit()

    // 创建socket连接
    term.write('正在连接...\r\n');
    // 获取当前请求的主机跟账号
    let id =  getUrl(window.location,'id')
    let account =  getUrl(window.location,'account')

    socket = new WebSocket('ws://127.0.0.1:8080/myssh?id='+id+'&account='+account+'&Authorization='+window.sessionStorage.getItem('Authorization'))
    // socket = new WebSocket('ws://162.14.109.53:8080/myssh')

    // 实现rz sz
    let zsentry = new Zmodem.Sentry({
        to_terminal: function (octets) { },  //i.e. send to the terminal
        on_detect: function (detection) {  // 判断当前输入的命令是上传或者下载 ,当 Sentry 检测到新的 ZMODEM 时
            console.log('新的zmodem')

            let zsession = detection.confirm();
            let promise;
            if (zsession.type === "receive") {
                promise = downloadFile(zsession);
                promise.catch(console.error.bind(console)).then(() => {
                });
            } else {
                upZsession = zsession
                //打开文件选择对话框
                showModal.value = true
                // promise = uploadFile()
            }

        },
        on_retract: function () { },
        sender: function (octets) { socket.send(new Uint8Array(octets)) }, // 即发送到对等的ZMODEM
    })

    socket.binaryType = "arraybuffer";

    // 打开socket监听事件的方法
    socket.onopen = function () {
        fitAddon.fit()
        term.onData(function (data) {
            console.log('输入：', data)
            // socket.send(JSON.stringify({ type: "stdin", data: getEncode64(data) })) //getEncode64
            socket.send(JSON.stringify({ type: "stdin", data: getEncode64(data) })) //getEncode64
        });
        message.success("会话成功连接！")
    }
    socket.onclose = function () {
        term.writeln('\r\n连接关闭');
    }
    socket.onerror = function (err) {
        // console.log(err)
        term.writeln('读取数据异常：', err);
    }
    // 接收数据
    socket.onmessage = function (recv) {
        try {
            console.log(typeof recv.data)
            if (typeof recv.data === 'object') {
                // rz sz 文件传输
                zsentry.consume(recv.data)
            } else {
                // 命令结果回显
                let msg = JSON.parse(recv.data);
                switch (msg.type) {
                    case "stdin":
                        // term.write(b64_to_utf8(msg.data))
                        // term.write(getDecode(msg.data))
                        // console.log(CryptoJS.enc.Base64.parse(msg.data).toString(CryptoJS.enc.Utf8))
                        term.write(CryptoJS.enc.Base64.parse(msg.data).toString(CryptoJS.enc.Utf8))

                        break;
                    case 'console':
                        console.log(CryptoJS.enc.Base64.parse(msg.data).toString(CryptoJS.enc.Utf8))

                    default:
                        break;
                }

            }

        } catch (e) {
            // console.log('unsupport data', recv.data)
            if (typeof recv.data === 'string') {
                let data = recv.data.split('"data":"')
                if (data.length > 0) {
                    let datacon = data[data.length - 1].split('"')
                    if (datacon.length > 0) {
                        // console.log(datacon[0])
                        message.error('读取数据乱码！')
                        term.write(atob(datacon[0]))
                    }
                }
            }
            console.log(e)

            // term.write(CryptoJS.enc.Base64.parse(recv.data).toString(CryptoJS.enc.Utf8))
        }
    }
    // 客户端发送大小改变，后端也需要跟着改变
    term.onResize(({ cols, rows }) => {
        socket.send(
            JSON.stringify({
                type: "resize",
                cols: cols,
                rows: rows
            })
        )
    })

    window.addEventListener("resize", () => {
        fitAddon.fit()
    }, false)

})

// onD

</script>

<style scoped>
.upload {
    min-height: 100px;
}
</style>